package com.hhd.flow.engine.cmd.impl;

import com.hhd.flow.engine.FlowEngine;
import com.hhd.flow.engine.cmd.FlowCommandExecutor;
import com.hhd.flow.engine.cmd.PassTaskContext;
import com.hhd.flow.engine.utils.CommonUtils;
import com.hhd.flow.engine.utils.FlowUtils;
import com.hhd.flow.engine.vo.FlowNode;
import com.hhd.flow.engine.vo.FlowNodeAndEdge;
import com.hhd.flow.engine.vo.Gateway;
import com.hhd.flow.entity.FlowGatewayExecution;
import com.hhd.flow.entity.FlowTask;
import com.hhd.flow.exception.FlowException;
import com.tang.script.command.BooleanCommand;
import com.tang.script.variable.VariablesContainer;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Setter
@Getter
@SuperBuilder(toBuilder = true)
public abstract class BaseCommand<T> extends PassAbleCommand<T> {
    private FlowEngine engine;
    private FlowCommandExecutor commandExecutor;
    private Map params = new HashMap();

    protected void passTask(PassTaskContext context) {
        loadContextData(context);
        FlowNode flowNode = context.getFlowModel().getNodeById(context.getFlowTask().getTaskDefinitionId());
        if (CommonUtils.isEmpty(flowNode))
            throw new FlowException("从流程图里没有找到任务定义: " + context.getFlowTask().getTaskDefinitionId() + "," + context.getFlowModel().getJsonModel().toJSONString());

        doPassTask(context, CommonUtils.asList(context.getFlowTask()));
    }

    private void doPassTask(PassTaskContext context, List<FlowTask> flowTasks) {
        while (!CommonUtils.isEmpty(flowTasks)) {
            List<FlowTask> temp = CommonUtils.newList();
            flowTasks.forEach(flowTask -> {
                if (canPass(flowTask, context)) {
                    temp.addAll(pass(flowTask, context));
                }
            });
            flowTasks = temp;
        }
    }

    private boolean canPass(FlowTask flowTask, PassTaskContext context) {
        FlowNode flowNode = context.getFlowModel().getNodeById(flowTask.getTaskDefinitionId());
        if (flowNode.getType() == FlowNode.FlowNodeType.start) return true;
        if (flowNode.getType() == FlowNode.FlowNodeType.end) return true;
        if (flowNode.getType() == FlowNode.FlowNodeType.userTask) return canPassUserTask(flowTask, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.empty) {
            return true;
        }
        if (flowNode.getType() == FlowNode.FlowNodeType.gateway) {
            //如果是开始网关, 那就能直接向下运转, 如果是结束网关, 那就得先判断是不是需要等待其他分支
            Gateway gateway = context.getFlowModel().getGatewaById(flowNode.getId());
            CommonUtils.isEmptyThrow(gateway, new FlowException("没有找到网关: " + flowNode.getId()));
            if (gateway.isStart()) return true;
            if (gateway.isEnd()) return canPassEndGateway(context, gateway, flowTask);
        }

        return false;
    }

    private boolean canPassEndGateway(PassTaskContext context, Gateway endGateway, FlowTask flowTask) {
        return flowTask.getExecuteCount() >= flowTask.getExecuteThreshold();
    }

    private boolean canPassUserTask(FlowTask flowTask, PassTaskContext context) {
        return flowTask.getExecuteCount() >= flowTask.getExecuteThreshold();
    }

    private List<FlowTask> pass(FlowTask flowTask, PassTaskContext context) {
        FlowNode flowNode = context.getFlowModel().getNodeById(flowTask.getTaskDefinitionId());
        onPassNode(flowTask, flowNode,context);
        if (flowNode.getType() == FlowNode.FlowNodeType.start) passStart(flowNode, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.userTask) passUserTask(flowNode, flowTask, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.end) passEndTask(flowNode, flowTask, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.empty) passEmptyTask(flowNode, flowTask, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.gateway) {
            Gateway gateway = context.getFlowModel().getGatewaById(flowNode.getId());
            if (gateway.isStart()) {
                passStartGateway(gateway, flowTask, context);
                return branchForStartGateway(gateway, flowTask, context);
            }
            if (gateway.isEnd()) passEndGateWay(flowNode, flowTask, context);
        }

        return passNextNodes(flowNode, context);
    }

    private List<FlowTask> branchForStartGateway(Gateway gateway, FlowTask flowTask, PassTaskContext context) {
        List<FlowTask> res = CommonUtils.newList();
        List<FlowNodeAndEdge> nodeAndEdges = context.getFlowModel().getNextNodeAndEdgesById(gateway.getId());
        nodeAndEdges = filterEdgeForGateway(nodeAndEdges, gateway, context);

        FlowGatewayExecution gateWayExecution = FlowUtils.createGateWayExecution(flowTask, gateway, context, nodeAndEdges.size());
        getEngine().getFlowGatewayExecutionService().save(gateWayExecution);

        for (FlowNodeAndEdge nodeAndEdge : nodeAndEdges) {
            res.addAll(enter(nodeAndEdge, context));
        }
        return res;
    }


    private List<FlowTask> passNextNodes(FlowNode flowNode, PassTaskContext context) {
        List<FlowTask> res = CommonUtils.newList();
        List<FlowNodeAndEdge> nodeAndEdges = context.getFlowModel().getNextNodeAndEdgesById(flowNode.getId());

        for (FlowNodeAndEdge nodeAndEdge : nodeAndEdges) {
            res.addAll(enter(nodeAndEdge, context));
        }
        return res;
    }

    private List<FlowNodeAndEdge> filterEdgeForGateway(List<FlowNodeAndEdge> nodeAndEdges,
                                                       Gateway gateway,
                                                       PassTaskContext context) {
        VariablesContainer variablesContainer = getVariablesContainer(context.getFormTypeId(), context.getFormId(), context.getFlowProcessInstance(), context.getFormModel());
        //单分支网关
        if (gateway.getExecuteWay() == Gateway.ExecuteWay.exclusive) {
            for (FlowNodeAndEdge nodeAndEdge : nodeAndEdges) {
                String formula = nodeAndEdge.getEdge().getFormula();
                if (CommonUtils.isEmpty(formula)) return CommonUtils.asList(nodeAndEdge);

                BooleanCommand booleanCommand = getEngine().getScriptCommandBuilder().newBooleanCommand()
                        .scriptText(formula).variablesContainer(variablesContainer).build();
                if (getEngine().executeScript(booleanCommand).getResult()) {
                    return CommonUtils.asList(nodeAndEdge);
                }
            }
            throw new FlowException("所有的分支条件均不满足, 无法向下继续执行");
        }

        //多分支网关
        List<FlowNodeAndEdge> res = CommonUtils.newList();
        for (FlowNodeAndEdge nodeAndEdge : nodeAndEdges) {
            String formula = nodeAndEdge.getEdge().getFormula();
            if (CommonUtils.isEmpty(formula)) {
                res.add(nodeAndEdge);
                continue;
            }

            BooleanCommand booleanCommand = getEngine().getScriptCommandBuilder().newBooleanCommand()
                    .scriptText(formula).variablesContainer(variablesContainer).build();
            if (getEngine().executeScript(booleanCommand).getResult()) {
                res.add(nodeAndEdge);
            }
        }
        CommonUtils.isEmptyThrow(res, new FlowException("所有的分支条件均不满足, 无法向下继续执行"));
        return res;
    }

    private List<FlowTask> enter(FlowNodeAndEdge nodeAndEdge, PassTaskContext context) {
        //添加路径
        savePath(context.addPath(nodeAndEdge.getEdge().getId()));
        FlowNode flowNode = nodeAndEdge.getNode();
        List<FlowTask> res = CommonUtils.newList();
        if (flowNode.getType() == FlowNode.FlowNodeType.userTask) res = enterUserTask(flowNode, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.end) res = enterEnd(flowNode, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.empty) res = enterEmpty(flowNode, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.gateway) {
            Gateway gateway = context.getFlowModel().getGatewaById(flowNode.getId());
            if (gateway.isStart()) res = enterStartGateway(gateway, context);
            if (gateway.isEnd()) res = enterEndGateway(gateway, context);
        }
        onEnterNode(res, flowNode, context);
        return res;
    }
}